﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;
using XUCore.Extensions;
using XUCore.Helpers;
using FileInfo = XUCore.Files.FileInfo;

namespace XUCore.NetCore.Uploads
{
    public static class UploadHelper
    {
        /// <summary>
        /// 处理图片
        /// 缩放比例<para></para>
        /// 裁剪原图<para></para>
        /// 图片水印<para></para>
        /// 文字水印<para></para>
        /// 缩略图裁剪<para></para>
        /// </summary>
        /// <param name="request"></param>
        /// <param name="imageInfo"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public static async Task<ImageFileInfo> ImageProcessingAsync(ImageUploadRequestBase request, ImageFileInfo imageInfo, CancellationToken cancellationToken)
        {
            imageInfo.Domain = request.Domain.SafeString();

            string orgin = Path.Combine(request.RootPath, imageInfo.FullPath);
            {
                using var source = Image.Load(orgin);
                //缩放比例
                if (request.IsZoomOriginal)
                {
                    source.Mutate(ctx =>
                    {
                        var ratio = request.Ratio / 100.0;
                        ctx.Resize((int)(source.Width * ratio), (int)(source.Height * ratio));
                    });
                }
                //裁剪原图
                if (request.IsCutOriginal)
                {
                    source.Mutate(ctx =>
                    {
                        var towidth = source.Width;
                        var toheight = source.Height;
                        if (source.Width >= source.Height && source.Width > request.AutoCutSize)
                        {
                            towidth = request.AutoCutSize;
                            toheight = source.Height * request.AutoCutSize / source.Width;
                        }
                        if (source.Height >= source.Width && source.Height > request.AutoCutSize)
                        {
                            towidth = source.Width * request.AutoCutSize / source.Height;
                            toheight = request.AutoCutSize;
                        }
                        ctx.Resize(new ResizeOptions
                        {
                            Mode = ResizeMode.Crop,
                            Size = new Size(towidth, toheight)
                        });
                    });
                }
                //图片水印
                if (request.WatherSettings != null && request.WatherSettings.IsWather == true)
                {
                    using var markImage = Image.Load(Path.Combine(request.RootPath, request.WatherSettings.Image));

                    markImage.Mutate(ctx =>
                    {
                        // 设置水印的大小，如果图片宽大于高， scale缩放的是水印图的宽度，否则将应用到高度上
                        float markWidth = source.Width * request.WatherSettings.Scale;
                        if (source.Width < source.Height)
                            markWidth = source.Height * request.WatherSettings.Scale;

                        // 对水印图片进行等比缩放
                        float ratio = markWidth / markImage.Width;
                        float markHeight = markImage.Height * ratio;
                        ctx.Resize((int)markWidth, (int)markHeight);
                    });

                    source.Mutate(ctx =>
                    {
                        var point = new Point();

                        if (request.WatherSettings.Position.Contains("center"))
                        {
                            point.X = (source.Width - markImage.Width) / 2;
                            point.Y = (source.Height - markImage.Height) / 2;
                        }

                        if (request.WatherSettings.Position.Contains("left"))
                            point.X = 0;

                        if (request.WatherSettings.Position.Contains("top"))
                            point.Y = 0;

                        if (request.WatherSettings.Position.Contains("right"))
                            point.X = source.Width - markImage.Width;

                        if (request.WatherSettings.Position.Contains("bottom"))
                            point.Y = source.Height - markImage.Height;

                        ctx.DrawImage(markImage, point, request.WatherSettings.Opacity);
                    });
                }
                //文字水印
                if (request.WatherFonts != null)
                {
                    source.Mutate(request.WatherFonts);
                }

                imageInfo.Width = source.Width;
                imageInfo.Height = source.Height;

                await source.SaveAsync(orgin, new JpegEncoder { Quality = request.Quality }, cancellationToken);

                //临时解决裁剪后的文件大小
                imageInfo.UpdateSize(new System.IO.FileInfo(orgin).Length);
            }
            // 处理缩略图
            {
                if (request.Thumbs == null || request.Thumbs.Count == 0)
                    return imageInfo;

                foreach (var thumbSize in request.Thumbs)
                {
                    var _size = thumbSize.Split('x', StringSplitOptions.RemoveEmptyEntries);
                    if (_size.Length < 2) continue;

                    var thumbPath = Path.Combine(imageInfo.Path, $"{imageInfo.Id.ToString().Replace("-", "")}-{thumbSize}.{imageInfo.Extension}");
                    var thumb = Path.Combine(request.RootPath, thumbPath);

                    using var source = Image.Load(orgin);

                    source.Mutate(ctx =>
                    {
                        var towidth = _size[0].ToInt();
                        var toheight = _size[1].ToInt();

                        ctx.Resize(new ResizeOptions
                        {
                            Mode = ResizeMode.Crop,
                            Position = AnchorPositionMode.Center,
                            Size = new Size(towidth, toheight)
                        });
                    });

                    var jpegEncoder = new JpegEncoder { Quality = 100 };

                    await source.SaveAsync(thumb, jpegEncoder, cancellationToken);

                    imageInfo.Thumbs.TryAdd(thumbSize, $"{XUCore.Helpers.Url.Combine(imageInfo.Domain, thumbPath)}");
                }
            }

            return imageInfo;
        }

        /// <summary>
        /// 保存文件
        /// </summary>
        /// <param name="formFile">表单文件</param>
        /// <param name="relativePath">相对路径</param>
        /// <param name="rootPath">根路径</param>
        /// <param name="cancellationToken">取消令牌</param>
        public static async Task<FileInfo> SaveAsync(IFormFile formFile, string relativePath, string rootPath,
            CancellationToken cancellationToken = default)
        {
            var date = DateTime.Now;

            var name = formFile.FileName;
            var size = formFile.Length;
            var path = System.IO.Path.Combine(relativePath, date.ToString("yyyyMMdd"));
            var id = Guid.NewGuid();
            var fileInfo = new FileInfo(path, size, name, id.ToString());
            fileInfo.SaveName = $"{id.ToString().Replace("-", "")}.{fileInfo.Extension}";

            var fullDir = System.IO.Path.Combine(rootPath, fileInfo.Path);
            if (!Directory.Exists(fullDir))
                Directory.CreateDirectory(fullDir);

            var fullPath = Path.Combine(fullDir, fileInfo.SaveName);
            fileInfo.Md5 = await SaveWithMd5Async(formFile, fullPath, cancellationToken);
            return fileInfo;
        }

        /// <summary>
        /// 保存文件
        /// </summary>
        /// <param name="formFile">表单文件</param>
        /// <param name="savePath">保存路径</param>
        /// <param name="cancellationToken">取消令牌</param>
        public static async Task SaveAsync(IFormFile formFile, string savePath, CancellationToken cancellationToken = default)
        {
            using (var stream = new FileStream(savePath, FileMode.Create))
            {
                await formFile.CopyToAsync(stream, cancellationToken);
            }
        }

        /// <summary>
        /// 保存文件并返回文件MD5值
        /// </summary>
        /// <param name="formFile">表单文件</param>
        /// <param name="savePath">保存路径</param>
        /// <param name="cancellationToken">取消令牌</param>
        public static async Task<string> SaveWithMd5Async(IFormFile formFile, string savePath,
            CancellationToken cancellationToken = default)
        {
            string md5;
            using (var stream = new FileStream(savePath, FileMode.Create))
            {
                md5 = Md5(stream);
                await formFile.CopyToAsync(stream, cancellationToken);
            }

            return md5;
        }
        /// <summary>
        /// 保存图片
        /// </summary>
        /// <param name="base64String">base64字符串</param>
        /// <param name="fileName">自定义base64的图片名字</param>
        /// <param name="relativePath">相对路径</param>
        /// <param name="rootPath">根路径</param>
        /// <param name="cancellationToken">取消令牌</param>
        public static async Task<ImageFileInfo> SaveImageAsync(string base64String, string fileName, string relativePath, string rootPath,
            CancellationToken cancellationToken = default)
        {
            var date = DateTime.Now;

            var id = Guid.NewGuid().ToString();
            var size = 0L;
            var path = Path.Combine(relativePath, date.ToString("yyyyMMdd"));
            var saveName = $"{id.Replace("-", "")}.jpg";

            if (fileName.NotEmpty() && !fileName.Contains(".jpg", StringComparison.CurrentCulture))
                fileName = $"{fileName}.jpg";

            var fullDir = Path.Combine(rootPath, path);

            if (!Directory.Exists(fullDir))
                Directory.CreateDirectory(fullDir);

            var fullPath = Path.Combine(fullDir, saveName);

            var md5 = "";

            using (var stream = new FileStream(fullPath, FileMode.Create))
            {
                byte[] bytes = Str.GetFromBase64String(base64String);

                await stream.WriteAsync(bytes, cancellationToken);

                size = bytes.Length;
                md5 = Md5(stream);
            }

            var imageInfo = new ImageFileInfo(path, size, fileName.IsEmpty() ? saveName : fileName, id);
            imageInfo.SaveName = saveName;
            imageInfo.Md5 = md5;

            return imageInfo;
        }

        /// <summary>
        /// 保存文件
        /// </summary>
        /// <param name="formFile">表单文件</param>
        /// <param name="relativePath">相对路径</param>
        /// <param name="rootPath">根路径</param>
        /// <param name="cancellationToken">取消令牌</param>
        public static async Task<ImageFileInfo> SaveImageAsync(IFormFile formFile, string relativePath, string rootPath,
            CancellationToken cancellationToken = default)
        {
            var date = DateTime.Now;

            var name = formFile.FileName;
            var size = formFile.Length;
            var path = System.IO.Path.Combine(relativePath, date.ToString("yyyyMMdd"));
            var id = Guid.NewGuid();
            var fileInfo = new ImageFileInfo(path, size, name, id.ToString());
            fileInfo.SaveName = $"{id.ToString().Replace("-", "")}.{fileInfo.Extension}";

            var fullDir = System.IO.Path.Combine(rootPath, fileInfo.Path);
            if (!Directory.Exists(fullDir))
            {
                Directory.CreateDirectory(fullDir);
            }

            var fullPath = Path.Combine(fullDir, fileInfo.SaveName);
            fileInfo.Md5 = await SaveWithMd5Async(formFile, fullPath, cancellationToken);
            return fileInfo;
        }

        /// <summary>
        /// MD5哈希
        /// </summary>
        /// <param name="stream">流</param>
        public static string Md5(Stream stream)
        {
            if (stream == null)
            {
                return string.Empty;
            }

            using (var md5Hash = MD5.Create())
            {
                return BitConverter.ToString(md5Hash.ComputeHash(stream)).Replace("-", "");
            }
        }
    }
}